package t2.client;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import t2.client.events.NDcmdEvent;
import t2.client.events.NDcmdEventHandler;
import t2.client.events.SendDataEvent;
import t2.client.rpc.LocalATcmd;
import t2.client.rpc.LocalATcmdAsync;

import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.allen_sauer.gwt.dnd.client.drop.AbsolutePositionDropController;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayInteger;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.gwt.typedarrays.client.JsUtils;
import com.google.gwt.typedarrays.shared.Int32Array;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DecoratorPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class MainTabPanel extends TabPanel {

    /* Used to handle inter-function communication for ND. */
    public static EventBus EVENT_BUS = GWT.create(SimpleEventBus.class);

    private Button buttonData = new Button("Submit");
    private final Button buttonScan = new Button("Scan");
    final XBeePanel xbeePanel = new XBeePanel();
    final PickupDragController dragController = new PickupDragController(RootPanel.get(), true);
    AbsolutePositionDropController dropController = new AbsolutePositionDropController(RootPanel.get());
    final LocalATcmdAsync atCmdService = (LocalATcmdAsync) GWT.create(LocalATcmd.class);
    DecoratorPanel dp = new DecoratorPanel();

    /* We send an HTTP request to get the results of an ND command.  
     * Store the returned array in a local list (jsonObjList) that has the same contents as
     *  the Network list on the server. */
    static List<JSONObject> jsonObjList = new ArrayList<JSONObject>();

    static private Map<int[], Map<String, String>> networkMap = new HashMap<int[], Map<String, String>>();

    /* Maintain the master list of serials.  These are also used to reference into the above networkMap. */
    static private List<int[]> allNodeSerialsList = new ArrayList<int[]>();

    final int[] dest64 = { 0, 0x13, 0xa2, 0, 0x40, 0x76, 0x35, 0x0e };

    public MainTabPanel() {
        super();

        FlowPanel fpData = new FlowPanel();
        FlowPanel fpAdmn = new FlowPanel();
        FlowPanel fpNetwork = new FlowPanel();

        final VerticalPanel vpData = new VerticalPanel();
        final VerticalPanel vpAdmn = new VerticalPanel();
        final VerticalPanel vpNetwork = new VerticalPanel();

        final AbsolutePanel apNetworkScanButtonPanel = new AbsolutePanel();
        final AbsolutePanel apNetworkStatusPanel = new AbsolutePanel();


        final Label prevPayload = new Label();
        final Label networkStatusLabel = new Label();
        final TextBox tb = new TextBox();


        /* Send raw data tab */
        vpData.add(tb);
        vpData.add(buttonData);
        vpData.add(prevPayload);


        /* Admin tab */
        vpAdmn.add(dp);


        /* Network tab */
        vpNetwork.add(apNetworkScanButtonPanel);
        vpNetwork.add(apNetworkStatusPanel);
        apNetworkScanButtonPanel.add(buttonScan);
        apNetworkStatusPanel.add(networkStatusLabel);


        fpData.add(vpData);
        fpAdmn.add(vpAdmn);
        fpNetwork.add(vpNetwork);


        add(fpData, "Send raw data");
        add(fpAdmn, "Admin");
        add(fpNetwork, "Network");
        selectTab(0);


        dragController.registerDropController(dropController);
        dragController.makeDraggable(xbeePanel, xbeePanel.imgXBee);

        buttonData.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                prevPayload.setText(tb.getText());
                T2.EVENT_BUS.fireEventFromSource(new SendDataEvent(dest64, stringToIntArray(tb.getText())),
                        getButtonData());
                tb.setText("");
            }
        });


        buttonScan.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                MainTabPanel.EVENT_BUS.fireEventFromSource(new NDcmdEvent(), buttonScan);
            }
        });


        MainTabPanel.EVENT_BUS.addHandler(NDcmdEvent.TYPE, new NDcmdEventHandler() {
            @Override
            public void processNDcmdEvent(NDcmdEvent event) {
                if (event.getSource() == buttonScan) {
                    networkStatusLabel.setText("");

                    executeQuery(new AsyncCallback<Response>() {

                        @Override
                        public void onFailure(Throwable caught) {}

                        @Override
                        public void onSuccess(Response result) {
                            getAllNodesList().clear();

                            JSONValue jsonValue = JSONParser.parseStrict(result.getText());
                            JSONArray jsonArray = jsonValue.isArray();

                            if (jsonArray == null) {
                                throw new RuntimeException("JSON payload didn't contain an object.");
                            }


                            /* Store the returned array in a local list that has the same contents as
                             *  the Network list on the server. */
                            jsonObjList.clear();
                            for (int i = 0; i < jsonArray.size(); i++) {
                                jsonObjList.add(jsonArray.get(i).isObject());
                            }


                            getNetworkMap().clear();
                            for (int i = 0; i < jsonObjList.size(); i++) {
                                NetworkDataClient ndc = jsonObjList.get(i).getJavaScriptObject().cast();

                                /* Extract this serial from returned text. */
                                int[] serial = jsonArrayToJavaIntArray(ndc.getSerial());

                                /* Add this serial to the global list of serials in the network. */
                                getAllNodesList().add(serial);

                                /* Organize the returned data in a map, and use the associated serial number
                                 * as the key to retrieve this data for each node. */
                                Map<String, String> attribMap = new HashMap<String, String>();
                                attribMap.put("NI", ndc.getNodeId());
                                attribMap.put("serial", toBase16(serial));
                                attribMap.put("type", String.valueOf(ndc.getDeviceType()));
                                networkMap.put(serial, attribMap);
                            }
                            dp.clear();
                            dp.add(new XBeeFormPanel());
                            networkStatusLabel.setText(printAllNetworkInfo());

                        }
                    });
                }
            }
        });
    }


    /* Handle the HTTP response asynchronously. */
    public void executeQuery(final AsyncCallback<Response> callback) {
        String JSON_URL = GWT.getModuleBaseURL() + "networkData?q=";
        String url = "";

        url = URL.encode(JSON_URL);
        try {
            RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
            builder.sendRequest(null, new RequestCallback() {

                @Override
                public void onError(Request request, Throwable exception) {}

                @Override
                public void onResponseReceived(Request request, Response response) {
                    callback.onSuccess(response);
                }
            });
        }
        catch (RequestException e) {}
    }

    /*******************************************************************/
    /* Uninteresting getters, setters, & utility functions below here. */
    public static int[] stringToIntArray(String s) {
        int[] intArr = new int[s.length()];

        for (int i = 0; i < s.length(); i++) {
            intArr[i] = s.charAt(i);
        }
        return intArr;
    }

    public static String toBase16(int[] arr) {
        return toBase16(arr, " ");
    }

    public static String toBase16(int[] arr, String delimiter) {

        if (arr == null) {
            return "";
        }

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < arr.length; i++) {
            sb.append(toBase16(arr[i]));

            if (i < arr.length - 1) {
                sb.append(delimiter);
            }
        }

        return sb.toString();
    }

    public static String toBase16(int b) {

        if (b > 0xff) {
            throw new IllegalArgumentException("input value [" + b + "] is larger than a byte");
        }

        if (b < 0x10) {
            return "0" + Integer.toHexString(b);
        } else {
            return Integer.toHexString(b);
        }
    }

    public int[] jsonArrayToJavaIntArray(JsArrayInteger jsonArray) {

        Int32Array int32serial = JsUtils.createInt32Array(jsonArray);
        int[] newArray = new int[int32serial.length()];
        for (int i = 0; i < int32serial.length(); i++) {
            newArray[i] = int32serial.get(i);
        }
        return newArray;
    }

    public String printAllNetworkInfo() {
        String output = "";
        int count = 0;

        Set<Entry<int[], Map<String, String>>> setOuter = getNetworkMap().entrySet();
        Iterator<Entry<int[], Map<String, String>>> iterOuter = setOuter.iterator();
        while (iterOuter.hasNext()) {
            Entry<int[], Map<String, String>> entryOuter = iterOuter.next();
            Set<Entry<String, String>> setInner = entryOuter.getValue().entrySet();
            Iterator<Entry<String, String>> iterInner = setInner.iterator();
            while (iterInner.hasNext()) {
                Entry<String, String> entryInner = iterInner.next();
                output += ++count;
                output += ": " + entryInner.getKey();
                output += ": " + entryInner.getValue();
            }
        }

        return output;
    }

    public Button getButtonData() {
        return buttonData;
    }

    public void setButtonData(Button buttonData) {
        this.buttonData = buttonData;
    }

    public static List<int[]> getAllNodesList() {
        return allNodeSerialsList;
    }


    public static void setAllNodesList(List<int[]> allNodes) {
        allNodeSerialsList = allNodes;
    }


    public static Map<int[], Map<String, String>> getNetworkMap() {
        return networkMap;
    }


    public static void setNetworkMap(Map<int[], Map<String, String>> network) {
        MainTabPanel.networkMap = network;
    }
}
